S04-03 Web-网络请求
[TOC]
前后端分离
服务器端渲染
服务器端渲染(SSR,Server Side Render):是指在服务器上将 JS 应用(通常是 React, Vue, Angular, Svelte 等构建的)转换并生成完整的 HTML 页面,然后将这个可以直接渲染的 HTML 发送给浏览器(客户端)的过程。
早期的网页都是通过后端渲染来完成的:
- 客户端发出请求 -> 服务端接收请求并返回相应HTML文档 -> 页面刷新,客户端加载新的HTML文档;
服务器端渲染的缺点:
会更新整个页面:当用户点击页面中的某个按钮向服务器发送请求时,页面本质上只是一些数据发生了变化,而此时服务器却要将重绘的整个页面再返回给浏览器加载,这显然有悖于程序员的“DRY( Don‘t repeat yourself )”原则;
网络开销大:而且明明只是一些数据的变化却迫使服务器要返回整个HTML文档,这本身也会给网络带宽带来不必要的开销。
有没有办法在页面数据变动时,只向服务器请求新的数据,并且在阻止页面刷新的情况下,动态的替换页面中展示的数据呢?
- 答案正是“AJAX”。
网页的渲染过程
服务器端渲染
前后端分离
HTTP
概念
HTTP
HTTP(HyperText Transfer Protocol,超文本传输协议):是互联网上应用最广泛的基础通信协议,用于在客户端(如浏览器)和服务器之间传输超文本(如网页、图片、视频等)。它是万维网(WWW)数据交换的基石,定义了数据如何打包、传输和解析。
维基百科:
HTTP 是一种用于分布式、协作式和超媒体信息系统的应用层协议;
HTTP是万维网的数据通信的基础,设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法;
通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识;
HTTP是一个客户端(用户)和服务端(网站)之间请求和响应的标准。
通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80),我们称这个客户端为
用户代理程序(User Agent)。响应的服务器上存储着一些资源,比如HTML文件和图像,我们称这个响应服务器为
源服务器(Origin Server)。
核心特性:
无状态(Stateless):
每次请求独立,服务器不记录客户端状态。需 Cookie/Session 维持登录状态。
统一通信标准:
不同设备/系统间通过标准协议交换数据。
HTTPS
HTTPS(HyperText Transfer Protocol Secure):是 HTTP 的安全版本,它在 HTTP 协议基础上增加了 TLS/SSL 加密层,用于保护客户端与服务器之间的数据传输。
HTTPS 的核心作用:
- 数据加密:通过 TLS/SSL 加密传输内容,防止中间人攻击(如窃听、篡改)。
- 身份验证:通过数字证书验证服务器身份,避免连接伪造的服务器。
- 数据完整性:防止传输过程中数据被恶意修改。
HTTPS 在 JS 中的关键机制:
- TLS/SSL 握手:客户端与服务器协商加密算法、交换密钥(JS 中由环境自动完成)。
- 证书验证:浏览器/Node.js 检查证书是否由受信任机构签发,域名是否匹配。
- 混合内容阻塞:浏览器会阻止 HTTPS 页面中加载 HTTP 资源(如脚本、图片)。
- HSTS:强制浏览器只通过 HTTPS 访问网站(响应头
Strict-Transport-Security
)。
URI
URI(Uniform Resource Identifiers,统一资源标识符):是一个用于唯一标识网络资源的字符串,它是 URL(统一资源定位符)和 URN(统一资源名称)的超集。
对比 URI/URL/URN:
在 Web 开发中,URL 是最常用的 URI 子集。
类型 | 说明 | 示例 |
---|---|---|
URI | 标识资源的通用字符串 | https://example.com/产品?id=123#详情 |
URL | 定位资源的 URI(含协议和路径) | https://example.com/page.html |
URN | 命名资源的 URI(不依赖位置) | urn:isbn:0451450523 (书籍永久标识) |
网页中的资源
我们网页中的资源通常是被放在 Web 资源服务器中,由浏览器自动发送 HTTP 请求来获取、解析、展示的。
目前我们页面中很多数据是动态展示的:
- 比如页面中的数据展示、搜索数据、表单验证等等,也是通过在 JS 中发送 HTTP 请求获取的;
HTTP 组成
一次 HTTP 请求主要包括:请求(Request)和响应(Response):
HTTP 请求:
HTTP 响应:
HTTP 版本
HTTP 的版本演进始终围绕 性能、安全与功能扩展 三大核心目标:
HTTP/0.9:
发布:1991
核心改进:
- 仅支持 GET 请求
- 仅支持获取文本数据
问题解决:基础原型
说明:主要是为了获取 HTML 页面内容
HTTP/1.0:
发布:1996
- 核心改进:
- 引入 HEAD/POST 方法、状态码、请求头、响应头
- 支持获取更多数据类型(不再局限于文本数据)
- 问题解决:功能扩展
- 说明:
- TCP短连接:浏览器的每次请求都需要与服务器建立一个TCP连接,请求处理完成后立即断开TCP连接,每次建立连接增加了性能损耗。
- 核心改进:
HTTP/1.1:
发布:1999
- 核心改进:持久连接、管道化、Host 头、分块传输
- 问题解决:性能优化(主流版本)
HTTP/2.0:
发布:2015
- 核心改进:二进制分帧、多路复用、头部压缩、服务器推送
- 问题解决:解决队头阻塞、提升速度
HTTP/3.0:
发布:2022
- 核心改进:基于 QUIC 协议(UDP)、0-RTT 握手
- 问题解决:减少延迟、优化弱网环境
HTTP 请求方式
在RFC 7231中定义了一组请求方式,用于定义客户端希望服务器执行的操作类型。
GET:获取资源。
POST:创建新资源。
PUT:完整替换资源。
DELETE:删除资源。
PATCH:部分更新资源。
CONNECT:建立一个到目标资源标识的服务器的隧道,通常用在代理服务器,网页开发很少用到。
TRACE:沿着到目标资源的路径执行一个消息环回测试。
OPTIONS:获取服务器支持的通信选项。用于预检请求(CORS)。
HEAD:获取资源元数据。请求一个与 GET 请求的响应相同的响应,但没有响应体。
- 比如在准备下载一个文件前,先获取文件的大小,再决定是否进行下载;
HTTP 请求头
HTTP 请求头(HTTP Request Headers):是客户端(如浏览器)向服务器发送请求时传递的元数据,它们为服务器提供请求的上下文信息、客户端偏好、认证凭证等。
核心特性:
大小限制:总请求头大小通常限制在 8-16KB
敏感信息:避免在请求头中传递敏感数据
CORS:自定义头需要服务器设置
Access-Control-Allow-Headers
jsAccess-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header
自定义请求头
自定义请求头的命名规范:
- 使用
X-
前缀(传统做法,非强制) - 语义化命名(如
X-Client-Version
) - 避免使用保留名称
- CORS:自定义头需要服务器设置
Access-Control-Allow-Headers
headers: {
'X-Client-ID': 'web-app-v1.2',
'X-Request-Source': 'mobile'
}
常见请求头
常见 HTTP 请求头:
类别 | 请求头示例 | 作用 |
---|---|---|
内容协商 | Accept , Accept-Language , Accept-Encoding | 指定客户端可接受的内容类型 |
身份认证 | Authorization , Cookie | 传递用户凭证 |
缓存控制 | Cache-Control , If-Modified-Since | 控制缓存行为 |
客户端信息 | User-Agent , Referer | 提供客户端环境信息 |
请求体信息 | Content-Type , Content-Length | 描述请求体格式和大小 |
跨域请求 | Origin | 标识请求来源 |
Content-Type
Content-Type:定义了请求体的数据格式,使服务器能够正确解析客户端发送的数据。
application/json:最常用,发送 JSON 格式数据
特点:
- 结构化数据,支持复杂对象
- 现代 API 首选格式
jsfetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Alice', age: 28 }) });
application/x-www-form-urlencoded:传统表单提交
特点:
- 键值对格式:
key1=value1&key2=value2
- URL 编码特殊字符
jsconst data = new URLSearchParams(); data.append('username', 'john_doe'); data.append('password', 'secure123'); fetch('/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data });
- 键值对格式:
multipart/form-data:文件上传和表单混合提交
特点:
- 支持二进制数据
- 每个字段有独立内容类型
- 需要 boundary 分隔符
- 注意:文件上传时不设置Content-Type,让浏览器自动处理可以包含必要的 boundary 信息
jsconst form = new FormData(); form.append('name', 'John'); form.append('avatar', fileInput.files[0]); fetch('/profile', { method: 'POST', body: form // 浏览器自动设置: // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123 });
text/plain:发送纯文本内容
特点:
- 无特殊格式处理
- 适用于简单文本数据
jsfetch('/api/log', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'Error: Invalid user input at 2023-08-10T14:30:00Z' });
application/xml:发送 XML 格式数据
特点:
- 传统企业系统常用
- 比 JSON 更冗长
jsconst xmlData = ` <user> <name>John</name> <age>30</age> </user> `; fetch('/legacy-api', { method: 'POST', headers: { 'Content-Type': 'application/xml' }, body: xmlData });
Content-Length
Content-Length:请求体大小(通常自动计算)
headers: {
'Content-Length': '348'
}
Connection
Connection:用于控制网络连接行为,决定了客户端与服务器之间的 TCP 连接如何处理。
- keep-alive:最常用,要求服务器保持连接持续(HTTP/1.1 默认开启)
- close:明确要求请求后关闭连接
- Upgrade:请求协议升级(如 HTTP → WebSocket)
- TE:指定传输编码(如分块传输)
- Trailers:声明将在分块消息后发送的尾部头字段
keep-alive 在 HTTP 不同版本中的差异:
http是基于TCP协议的,但是通常在进行一次请求和响应结束后会立刻中断;
- HTTP1.0:
- 默认关闭连接
- 需要显式声明
Connection: keep-alive
HTTP1.1:
- 默认启用持久连接
- 空闲超时自动关闭(通常 5-15 秒)
HTTP2.0:
- 完全移除
Connection
头 - 通过多路复用(Multiplexing)自动管理连接
- 单个 TCP 连接处理所有请求
- 完全移除
Keep-Alive
Keep-Alive:控制持久连接的细节。
- timeout:空闲连接保持时间(秒)
- max:连接关闭前最大请求数
// Connection 与 Keep-Alive 的配合使用
Connection: keep-alive
Keep-Alive: timeout=5, max=100
Accept
Accept:指定客户端可接受的 MIME 类型:
headers: {
'Accept': 'application/json, text/plain, */*'
}
Accept-Encoding
Accept-Encoding:指定客户端支持的文件压缩格式。比如 js 文件可以使用 gzip 编码,对应 .gz 文件。
headers: {
'Accept-Encoding': 'gzip, deflate, br'
}
User-Agent
User-Agent:标识客户端相关信息:
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
}
// Chrome:
// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
// Egdge:
// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0'
HTTP 状态码
HTTP 状态码(HTTP Status Code):是服务器响应请求时返回的三位数字代码,用于表示请求的处理结果。
HTTP 状态码非常多,可以根据不同的情况,给客户端返回不同的状态码;
MDN响应码解析地址:https://developer.mozilla.org/zh-CN/docs/web/http/status
状态码分类体系:
HTTP 状态码分为五类,通过首位数字标识:
分类 | 范围 | 含义 | JavaScript 处理重点 |
---|---|---|---|
1xx | 100-199 | 信息响应 | 临时响应,前端通常自动处理 |
2xx | 200-299 | 成功响应 | 正常处理成功响应 |
3xx | 300-399 | 重定向 | 自动跟随或手动处理重定向 |
4xx | 400-499 | 客户端错误 | 处理用户端问题(如无效请求) |
5xx | 500-599 | 服务器错误 | 处理服务端故障 |
常见状态码:
状态码 | 状态描述 | 信息说明 |
---|---|---|
200 | OK | 请求成功完成 |
201 | Created | 资源创建成功 |
204 | No Content | 请求成功但无返回内容 |
301 | Moved Permanently | 资源永久迁移 |
302 | Found | 资源临时移动 |
304 | Not Modified | 资源未修改(缓存有效) |
400 | Bad Request | 请求语法错误 |
401 | Unauthorized | 未认证/缺少凭证 |
403 | Forbidden | 无权限访问 |
404 | Not Found | 资源不存在 |
429 | Too Many Requests | 请求过于频繁 |
500 | Internal Server Error | 通用服务器错误 |
502 | Bad Gateway | 上游服务器无效响应 |
503 | Service Unavailable | 服务暂时不可用 |
HTTP 响应头
HTTP 响应头(HTTP Response Headers):是服务器在响应客户端请求时发送的元数据信息,它们提供了关于响应内容的关键信息和控制指令。
常见 HTTP 响应头:
内容信息类:
- Content-Type:声明响应体的 MIME 类型。常见值:
text/html; charset=utf-8
application/json
image/png
- Content-Length:响应体的字节大小
- Content-Type:声明响应体的 MIME 类型。常见值:
缓存控制类:
- Cache-Control:控制缓存行为。常用指令:
max-age=3600
:缓存有效期(秒)。用于静态资源缓存no-cache
:需重新验证。用于动态内容no-store
: 禁止缓存。用于 敏感数据public
:允许代理缓存。用于 CDN 资源private
:仅浏览器缓存。用于用户私有数据
- ETag / Last-Modified:资源验证标识
- Cache-Control:控制缓存行为。常用指令:
安全策略类:
Content-Security-Policy (CSP):防止 XSS 攻击。
常见配置:
httpContent-Security-Policy: default-src 'self'; script-src 'self' https://apis.example.com
Strict-Transport-Security (HSTS):强制 HTTPS 连接
配置:
httpStrict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options:防止点击劫持。取值:
DENY
:完全禁止嵌入SAMEORIGIN
:仅同源可嵌入
CORS (跨域资源共享) 类:
Access-Control-Allow-Origin:允许跨域的源
配置:
httpAccess-Control-Allow-Origin: https://yourdomain.com Access-Control-Allow-Origin: * // 允许所有源
Access-Control-Expose-Headers:允许前端访问的自定义头
Access-Control-Allow-Headers:允许前端访问的响应头
性能优化类:
- Content-Encoding:响应体压缩算法。常见值:
gzip
br
(Brotli)deflate
- Connection:控制连接持久性
- Content-Encoding:响应体压缩算法。常见值:
插件-FeHelper
为了之后查看数据更加的便捷、优雅,我们安装一个Chrome插件:
方式一:Chrome扩展商店安装:https://chromewebstore.google.com/category/extensions
方式二:手动安装:https://github.com/zxlie/FeHelper/tree/master/apps/static/screenshot/crx
AJAX
AJAX 概述
AJAX(Asynchronous JavaScript And XML):是一种用于创建异步 Web 应用的核心技术,它允许网页在不重新加载整个页面的情况下与服务器交换数据并更新部分内容。
AJAX 定义与本质:
- AJAX 不是单一技术,而是多种技术的组合:
- HTML/CSS:内容展示
- DOM:动态内容操作
- XMLHttpRequest:数据传输
- JSON/XML:数据格式
- 异步通信:在后台与服务器交换数据,不阻塞用户界面
核心特性:
优点:
- 用户体验提升:无刷新更新内容
- 性能优化:减少带宽使用,只传输必要数据
- 异步处理:不阻塞用户界面
- 模块化:前后端分离开发
缺点:
- SEO 问题:搜索引擎爬虫难以索引动态内容
- 浏览器历史:后退按钮行为需要额外处理
- 跨域限制:需要 CORS 或代理解决
- 调试复杂:异步流程增加调试难度
AJAX 核心实现方式:
- 方式一:XMLHttpRequest (XHR)
- 方式二:Fetch
XHR
API-XHR
构造方法:
- new XMLHttpRequest():
()
,用于创建一个 XHR 实例对象。用于与服务器交互,支持异步 HTTP 请求(如 AJAX)。
属性:
- xhr.onreadystatechange:
fn
,事件处理,用于监听请求状态变化。会在readyState
属性值变化时触发,是处理异步请求响应的传统方式。 - xhr.readyState:
0|1|2|3|4
,只读,表示请求的当前状态。 - xhr.status:
number
,只读,表示 HTTP 响应的状态码。 - xhr.statusText:
string
,只读,用于获取 HTTP 响应的状态文本。 - xhr.response:
any
,只读,用于获取 HTTP 响应的内容主体。可以根据responseType
设置自动解析响应内容。 - xhr.responseType:
string
,默认:'text'
,可读写,用于指定服务器响应的数据类型,并控制如何解析响应数据。 - xhr.timeout:
number
,默认:0
,可读写,用于设置 HTTP 请求的超时时间(毫秒)。超时后请求自动终止并触发timeout
事件。 - xhr.withCredentials:
boolean
,默认:false
,可读写,用于控制跨域请求是否携带用户凭证(如 cookies、HTTP 认证信息)。
方法:
- xhr.open():
(method,url,async?,user?,password?)
,用于初始化 HTTP 请求配置(但不会发送请求)。需配合send()
方法使用。 - xhr.send():
(body?)
,用于实际发送 HTTP 请求到服务器。必须在open()
之后调用。 - xhr.abort():
()
,用于立即终止正在进行的 HTTP 请求。调用后,请求会被取消,不会继续接收响应数据。 - xhr.setRequestHeader():
(header, value)
,用于在发送请求前设置 HTTP 请求头。确保在open()
之后、send()
之前调用。 - xhr.getResponseHeader():
(headerName)
,用于获取指定 HTTP 响应头的值。
事件:
- loadstart:
(event)=>void
,首个事件,在浏览器开始加载服务器响应时触发。 - load:
(event)=>void
,核心成功事件,当请求成功完成且响应数据完全接收时触发。 - loadend:
(event)=>void
,最终事件,在请求结束时触发(无论成功或失败)。用于清理资源和执行最终操作。 - progress:
,
- abort:
,
- error:
,
- timeout:
,
基本使用
new XMLHttpRequest():()
,用于创建一个 XHR 实例对象。用于与服务器交互,支持异步 HTTP 请求(如 AJAX)。
xhr.open():(method,url,async?,user?,password?)
,用于初始化 HTTP 请求配置(但不会发送请求)。需配合 send()
方法使用。
xhr.send():(body?)
,用于实际发送 HTTP 请求到服务器。必须在 open()
之后调用。
xhr.onreadystatechange:fn
,事件处理,用于监听请求状态变化。会在 readyState
属性值变化时触发,是处理异步请求响应的传统方式。
AJAX基本使用:
第一步:创建网络请求的AJAX对象(使用XMLHttpRequest)
第二步:监听XMLHttpRequest对象状态的变化,或者监听onload事件(请求完成时触发);
第三步:配置网络请求(通过open方法);
第四步:发送send网络请求;
XHR 请求状态 readyState
xhr.readyState:0|1|2|3|4
,只读,表示请求的当前状态。
readyState 状态详解:
事实上,我们在一次网络请求中看到状态发生了很多次变化,这是因为对于一次请求来说包括如下的状态:
XHR 执行流程:
- 准备阶段:
- 调用
open()
初始化请求配置 readyState
变为1
(OPENED)- 设置请求头(可选)
- 注册事件处理器
- 调用
- 发送阶段:
send()
被调用- 请求进入发送队列
- 传输阶段:
- 服务器接收请求
readyState
变为2
(HEADERS_RECEIVED)readyState
变为3
(LOADING) 数据接收中
- 完成阶段:
readyState
变为4
(DONE)- 触发
load
/error
/abort
事件
对比 status:
readyState
:记录的是 XMLHttpRequest 对象的状态变化,而非 HTTP 状态码。status
:记录的是 HTTP 状态码。
发送同步请求(已废弃):
将 open()
的第三个参数设置为 false
。
XHR 事件
XHR 事件:
除了onreadystatechange还有其他的事件可以监听:
- loadstart:
(event)=>void
,首个事件,在浏览器开始加载服务器响应时触发。 - load:
(event)=>void
,核心成功事件,当请求成功完成且响应数据完全接收时触发。 - loadend:
(event)=>void
,最终事件,在请求结束时触发(无论成功或失败)。用于清理资源和执行最终操作。 - progress:
,
- abort:
,
- error:
,
- timeout:
,
XHR 请求生命周期
示例:使用 load 监听获取数据:
响应数据和响应类型
响应数据:
发送了请求后,我们可以通过 response 获取响应的正文内容。
xhr.response:any
,只读,用于获取 HTTP 响应的内容主体。可以根据 responseType
设置自动解析响应内容。
响应类型:
response 返回的数据类型取决于 responseType 的属性设置。
xhr.responseType:string
,默认:'text'
,可读写,用于指定服务器响应的数据类型,并控制如何解析响应数据。
示例:设置响应类型
对比 responseText、responseXML:
早期:服务器返回的数据通常是普通的
text
和XML
类型:所以会通过
responseText
、responseXML
来获取响应结果。之后将它们转化成 JS 对象形式。
jsxhr.responseType = 'text' xhr.onload = function() { console.log(xhr.responseText) console.log(xhr.responseXML) }
目前:服务器基本返回的数据都是
json
类型:- 直接设置为
json
即可
jsxhr.responseType = 'json'
- 直接设置为
HTTP 响应状态 status
XMLHttpRequest的 readyState
是用于记录 xhr 对象本身的状态变化,并非针对于 HTTP 的网络请求状态。
如果我们希望获取HTTP响应的网络状态,可以通过 status
和 statusText
来获取。
xhr.status:number
,只读,表示 HTTP 响应的状态码。
xhr.statusText:string
,只读,用于获取 HTTP 响应的状态文本。
示例:
打印 HTTP 响应状态
HTTP 响应状态检查
jsxhr.onreadystatechange = function() { if (this.readyState === 4) { // 主成功范围 (200-299) if (this.status >= 200 && this.status < 300) { console.log("请求成功"); } else { console.error(`请求失败: ${this.status} ${this.statusText}`); } } };
参数传递方式@
在开发中,我们使用最多的是GET和POST请求,在发送请求的过程中,我们也可以传递数据给服务器。
参数传递的方式:
常见的传递数据的方式有如下几种:
方式1:GET 请求 query 参数
方式2:POST 请求 x-www-form-urlencoded 格式
方式3:POST 请求 FormData 格式
方式4:POST 请求 JSON 格式
方式1:GET 请求 query 参数
open()
中参数以 查询字符串(Query String)形式传递。- GET 请求时
send()
参数为空。
方式2:POST 请求 x-www-form-urlencoded 格式
open()
中参数不携带查询字符串。通过
setRequestHeader()
设置 Content-Type 请求头为x-www-form-urlencoded
。send()
方法中携带 查询字符串 形式的参数。
方式3:POST 请求 FormData 格式
open()
中参数不携带查询字符串。- 不要设置 Content-Type。
send()
方法中携带 FormData 实例对象。
方式4:POST 请求 JSON 格式
open()
中参数不携带查询字符串。- 通过
setRequestHeader()
设置 Content-Type 请求头为applicaiton/json; chartset=utf-8
。 send()
方法中携带JSON.stringify(obj)
转化后的对象。
封装:AJAX 网络请求@
基本实现-GET(无参)
调用方法:
实现代码:GET 请求(无参)
基本实现-GET(传参)
调用方法:
方式1:通过 url 传递参数
方式2:通过 data 传递参数
代码实现:GET 请求(传参):
思路:拼接 data 为查询字符串,兼容通过 data 传递参数
基本实现-POST
调用方法:
实现代码:POST 请求
思路:
- 设置
Content-Type
为application/json
send()
调用时传递JSON.stringify(data)
转换后的参数
优化:Promise
问题:回调地狱
回调地狱:第2次请求依赖第1次请求结果时会出现回调地狱的问题。
上述使用回调函数的实现方式在使用的时候会出现回调地狱的问题:
解决:使用 Promise 的方式实现可以解决该问题。
调用方法:
实现代码:
- 使用 Promise 包裹之前的代码
resolve()
成功的结果reject()
失败的结果
优化:timeout
xhr.timeout:number
,默认:0
,可读写,用于设置 HTTP 请求的超时时间(毫秒)。超时后请求自动终止并触发 timeout
事件。
在网络请求的过程中,为了避免过长的时间服务器无法返回数据,通常我们会为请求设置一个超时时间:timeout
。
当达到超时时间后依然没有获取到数据,那么这个请求会自动被取消掉;
默认值为0,表示没有设置超时时间;
实现代码:
优化:取消请求
xhr.abort():()
,用于立即终止正在进行的 HTTP 请求。调用后,请求会被取消,不会继续接收响应数据。
我们可以通过 abort()
方法强制取消请求。
调用方法:
实现代码:
回调形式思路:返回 xhr 对象实例,通过它调用 xhr.abort() 方法取消请求。
Promise形式思路:
通过为 promise 实例对象添加 xhr 属性,将 xhr 对象返回出去
优化:headers【
【TODO:自己实现】
Fetch
API-Fetch
Fetch
方法:
- window.fetch():
(url, options?)
,用于发起网络请求的现代 API,基于 Promise 设计,取代了传统的 XMLHttpRequest。
Response
属性:
方法:
- response.json():
()
, - response.text():
()
, - response.blob():
()
, - response.arrayBuffer():
()
, - response.fromData():
()
,
Fetch 概述
Fetch API:是一个现代、强大且灵活的 JS 接口,用于在浏览器环境中发起异步网络请求(如 HTTP 请求)。它取代了陈旧的 XMLHttpRequest
(XHR),提供了一个 基于 Promise 的、更简洁直观的方法来获取资源(无论是从一个 URL 获取数据,还是向服务器发送数据)。
Fetch 的优势 / 局限性:
优势:
- 基于 Promise: 语法清晰,避免了回调地狱。
- 语法简洁直观: 比
XMLHttpRequest
更容易使用。 - 内置 JSON 解析: 通过
.json()
方法轻松处理 JSON 数据。 - 符合现代标准: 是 W3C 规范,得到所有现代浏览器支持。
- 强大的配置能力: 通过
Request
和Headers
对象提供精细控制。
局限性:
- 默认不携带 Cookie: 需要显式设置
credentials: 'include'
。 - HTTP 错误不会导致 Reject: 需要手动检查
response.ok
。 - 没有内置的超时 (timeout) 机制: 需要使用
AbortController
实现。 - 没有原生请求进度监控: 对于大文件上传/下载,监控进度比较麻烦(可通过
response.body
实现,但复杂)。 - 浏览器兼容性: 虽然现代浏览器都支持,但在非常旧的浏览器(如 IE)中完全不可用。
对比 XHR / Axios:
特性 | Fetch API | XMLHttpRequest (XHR) | Axios |
---|---|---|---|
语法 | 基于 Promise,简洁现代 | 基于事件的回调,冗长复杂 | 基于 Promise,简洁优雅 |
HTTP 错误处理 | 不会 reject,需手动检查 | 在事件回调中手动检查 | 会自动 reject,更简单 |
JSON 转换 | 需要调用 .json() 方法 | 需要手动 JSON.parse() | 自动转换请求和响应数据 |
请求取消 | 使用 AbortController | 使用 .abort() 方法 | 使用 AbortController 或自定义 cancel token |
请求/响应拦截 | 无原生支持 | 无原生支持 | 有内置支持,非常强大 |
浏览器支持 | 现代浏览器 | 所有浏览器,包括 IE | 现代浏览器(通过 polyfill 可支持旧浏览器) |
进度监控 | 较难实现 | 原生支持 | 浏览器端原生支持,Node.js 端也可用 |
基本使用
window.fetch():(url, options?)
,用于发起网络请求的现代 API,基于 Promise 设计,取代了传统的 XMLHttpRequest。
基本语法:
Fetch API 的核心是全局的 fetch()
函数。它接收一个必需的参数:资源的 URL。它返回一个 Promise 对象,这个 Promise 在请求完成后会解析为一个 Response 对象。
由于它基于 Promise,你可以优雅地使用 .then()
和 .catch()
进行链式调用,或者使用更现代的 async/await
语法。
fetch(url) // 发起一个默认的 GET 请求
.then(response => {
// 处理响应
})
.catch(error => {
// 处理网络错误
});
基本示例:
使用 then
jsfetch('https://api.example.com/data') .then(response => { if (!response.ok) { // 如果状态码不是 2xx throw new Error(`HTTP 错误! 状态码: ${response.status}`); } return response.json(); // 返回解析 JSON 的 Promise }) .then(data => { console.log(data); // 这里获取到最终的数据 }) .catch(error => { console.error('请求失败:', error); });
使用 async/await
jsasync function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); // 解析 JSON 响应体 console.log(data); } catch (error) { console.error('获取数据失败:', error); } }
请求配置
常见的请求配置:
- method:请求方法,如
'GET'
,'POST'
,'PUT'
,'DELETE'
。 - headers:一个对象,用于设置请求头(例如
{'Content-Type': 'application/json'}
)。 - body:请求体数据。通常是 JSON 字符串、FormData、URLSearchParams 等。GET 和 HEAD 请求不能包含 body。
- mode:请求的模式,如
'cors'
,'no-cors'
,'same-origin'
。 - credentials:是否发送 cookies。如
'include'
(总是发送)、'same-origin'
(同源时发送)、'omit'
(不发送)。
示例:设置请求配置发送 POST 请求
const userData = {
name: 'John Doe',
email: 'john@example.com'
};
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 告诉服务器我们发送的是 JSON
},
body: JSON.stringify(userData) // 将 JavaScript 对象转换为 JSON 字符串
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));
响应对象 Response
Fetch 数据响应阶段:Fetch 的数据响应主要分为两个阶段:
阶段1:服务器返回响应 response:
fetch() 返回的 promise 就使用内置类 Response 来对响应头进行解析
检查 HTTP 状态码:通过检查响应头中的 HTTP 状态码以确定请求是否成功。
reject 的情况:
无法建立 HTTP 请求,如网络问题或请求网址不存在。
异常 HTTP 状态码,如 404 或 500,不会导致被 reject。
我们可以在 response 的属性中查看 HTTP 状态:
阶段2:获取 response 中的数据:
我们需要使用一个其他的方法调用
response.json():
()
,response.text():
()
,
发起请求
fetch()
的第二个参数是一个可选的 选项对象(options object),你可以用它来配置请求,包括设置请求方法(method)、请求头(headers)、请求体(body)等。
POST 发送 JSON
配置请求:
- 设置 method:将请求方式设置为 POST
- 设置 headers:告诉服务器我们发送的是 JSON
- 设置 body:将 JS 对象转换为 JSON 字符串
const userData = {
name: 'John Doe',
email: 'john@example.com'
};
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 告诉服务器我们发送的是 JSON
},
body: JSON.stringify(userData) // 将 JavaScript 对象转换为 JSON 字符串
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));
POST 发送 FormData
配置请求:提交表单数据(表单中有文件上传)
- 设置 method:将请求方式设置为 POST
- 不要设置 headers:Content-Type 会自动设置为 multipart/form-data,无需手动设置
- 设置 body:创建 FormData 实例对象赋值给 body
const formData = new FormData();
formData.append('username', 'john_doe');
formData.append('avatar', fileInput.files[0]); // 假设有一个文件输入框
fetch('https://api.example.com/profile', {
method: 'POST',
body: formData // Content-Type 会自动设置为 multipart/form-data,无需手动设置
});
POST 发送 x-www-form-urlencoded
配置请求:提交表单数据
- 设置 method:将请求方式设置为 POST
- 设置 headers:设置 Content-Type 为 application/x-www-form-urlencoded
- 设置 body:将查询字符串赋值给 body
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'name=tom&age=18'
})
.then(response => response.jons())
.then(data => console.log(data))
错误处理机制
Fetch 的错误处理需要特别注意:
- 只有当网络故障或请求无法完成时,Promise 才会被 reject(例如,网络断开、域名解析失败)。
- 对于 HTTP 错误状态(如 404 Not Found 或 500 Internal Server Error),Promise 不会被 reject。相反,
response.ok
属性会变为false
。
因此,你必须手动检查 response.ok
或 response.status
来抛出错误。
示例:完整的错误处理模式
fetch('https://api.example.com/not-found-page')
.then(response => {
if (!response.ok) { // 处理 HTTP 错误
throw new Error(`服务器返回了 ${response.status} 错误`);
}
return response.json();
})
.then(data => {
// 处理成功的数据
})
.catch(error => {
// 这里会捕获两种错误:
// 1. 由 throw 抛出的 HTTP 错误
// 2. 网络本身导致的错误
console.error('请求完全失败:', error);
});
最佳实践
最佳实践:
总是检查
response.ok
: 不要假设请求一定会成功。使用 async/await: 让异步代码看起来像同步代码,更易读。
处理网络错误和 HTTP 错误: 确保
.catch()
能捕获所有类型的失败。设置正确的
Content-Type
请求头: 特别是发送 JSON 时,一定要设置'Content-Type': 'application/json'
。注意 CORS: 向不同域的服务器发起请求时,需要服务器正确配置 CORS 头,否则请求会被浏览器阻止。
实现请求超时: 使用
AbortController
。js// 1. 创建 AbortController 对象,并在5秒后调用它的 abort() 取消方法 const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒后超时 fetch(url, { // 2. 绑定 AbortController 对象到 fetch 的请求配置参数 signal 上 signal: controller.signal }).then(...).catch(err => { // 3. 捕获取消请求后的的错误 if (err.name === 'AbortError') { console.log('请求超时'); } }).finally(() => { // 4. 始终清理创建的定时器 clearTimeout(timeoutId); });
文件上传-前端
文件上传是开发中经常遇到的需求,比如头像上传、照片等。
要想真正理解文件上传,必须了解服务器如何处理上传的文件信息。
使用:
文件上传-XHR
代码实现:
文件上传-Fetch
Fetch 也支持文件上传,但是 Fetch 没办法监听进度。
代码实现: